"""Provides various widgits, which can automatically draw things on screen"""

import collections
from pyre.core import types
from pyre.view import fov

class Widgit(object):
    """Base class for all auto-updating Widgits"""

    def __init__(self):
        self._pos = None
        self._dim = None

    def update(self, term):
        """
        Widgit.update(Terminal): None
        Asks the Widgit to update the terminal buffer to reflect the current
        data state of the Widgit data source
        """

    def draw(self, term, relpos, char):
        """
        Widgit.draw(Terminal, Coordinate, ColoredChar): None
        Used by the Widgit to draw a character relative to its position on the
        the screen
        """

        assert self.inbounds(relpos)

        term.buffer_char(self._pos + relpos, char)

    def place_widgit(self, pos, dim):
        """
        Widgit.place_widgit(Coordinate, Coordinate): None
        Moves the Widgit and resizes it
        """

        self.move(pos)
        self.resize(dim)

    def move(self, pos):
        """
        Widgit.move(Coordinate):
        Moves the position of the Widgit on the screen. The given position is
        the upper left corner of the Widgit.
        """

        self._pos = pos

    def resize(self, dim):
        """
        Widgit.resize(Coordinate): None
        Sets the dimension of the Widgit
        """

        self._dim = dim

    def inbounds(self, relpos):
        """
        Widgit.inbounds(Coordinate): bool
        Returns True if the relative position is within the dimension of the
        Widgit.
        """

        return 0 <= relpos[0] < self._dim[0] and 0 <= relpos[1] < self._dim[1]


class Camera(Widgit):
    """Tracks and draws the field of view for an Actor"""

    def __init__(self, actor, radius=None, fov_func=fov.default):
        Widgit.__init__(self)
        self.actor = actor
        self.fov_func = fov_func
        self.radius = radius
        self.center = None

        self.mem_local = set()
        self.mem_world = None

    def update(self, term):
        offset = self.center - self.actor.pos

        # draw from memory
        if self.mem_world is self.world:
            forgotten = set()
            for pos in self.mem_local:
                if self.inbounds(pos + offset):
                    tile = self.world.tile_at(pos).recolored(types.Color.SMOKE)
                    self.draw(term, pos + offset, tile)
                else:
                    forgotten.add(pos)
            self.mem_local -= forgotten
        else:
            self.mem_world = self.world
            self.mem_local.clear()

        # draw visible
        for pos in self.calc_fov():
            self.draw(term, pos + offset, self.world.look(pos))
            self.mem_local.add(pos)

    def resize(self, dim):
        Widgit.resize(self, dim)
        self.center = types.Coordinate((dim[0] - 1) // 2, (dim[1] - 1) // 2)
        if self.radius is None or self.radius > min(self.center):
            self.radius = min(self.center)

    def draw_relative(self, term, world_pos, tile):
        screen_pos = world_pos + self.center - self.actor.pos
        if self.inbounds(screen_pos):
            self.draw(term, screen_pos, tile)

    def calc_fov(self):
        return self.fov_func(self.world, self.pos, self.radius)

    @property
    def pos(self):
        return self.actor.pos

    @property
    def world(self):
        return self.actor.world


class MessageBox(Widgit):
    """Displays the most recent messages of a particular messenger"""

    def __init__(self, messenger, history_size=100):
        Widgit.__init__(self)
        self.messenger = messenger
        history_init = [('', 0)] * history_size
        self.history = collections.deque(history_init, maxlen=history_size)

    def update(self, term):
        messages = self.messenger.retrieve_collapsed()
        num_new = len(messages)

        if num_new > 0:
            if self.history[-1][0] == messages[0][0]:
                text, old_count = self.history[-1]
                _, new_count = messages[0]
                messages = messages[1:]
                self.history[-1] = text, old_count + new_count
            self.history += messages

        for y, hist_item in zip(range(self._dim[1]), reversed(self.history)):
            color = types.Color.GRAY if y >= num_new else types.Color.WHITE
            text, count = hist_item
            message = '{} (x{})'.format(text, count) if count > 1 else text
            for x, char in enumerate(message[:self._dim[0]]):
                rev_y = self._dim[1] - y - 1
                self.draw(term, types.Coordinate(x, rev_y), 
                                types.ColoredChar(char, color))

    def show_history(self, term):
        term.save_buffer()
        term.clear_buffer()

        for y, hist_item in zip(range(term.rows), reversed(self.history)):
            text, count = hist_item
            message = '{} (x{})'.format(text, count) if count > 1 else text
            for x, char in enumerate(message[:term.cols]):
                rev_y = term.rows - y - 1
                term.buffer_char(types.Coordinate(x, rev_y), 
                                 types.ColoredChar(char))

        term.refresh_screen()
        term.get_key()
        term.recall_buffer()
        term.refresh_screen()


class TextDisplay(Widgit):
    """Displays data driven text in a box"""

    def __init__(self, text_func):
        Widgit.__init__(self)
        self.text_func = text_func

    def update(self, term):
        text = self.text_func()
        x, y = 0, 0
        for char in text:
            if char == '\n':
                x, y = 0, y + 1
            else:
                self.draw(term, types.Coordinate(x, y), 
                                types.ColoredChar(char))

                x += 1
                if x == self._dim[0]:
                    x, y = 0, y + 1
